#include "ThreadCache.h"
#include "CentralCache.h"

// ThreadCache用来申请内存的函数
// 参数1：size-申请的内存大小
// 返回值：void*类型的指针，指向申请到的内存
void *ThreadCache::Allocate(size_t size)
{
    assert(size <= MAX_BYTES);                   // 断言申请的内存大小是否小于最大值
    size_t alignSize = SizeClass::RoundUp(size); // 把要申请的内存大小，进行向上对齐
    size_t index = SizeClass::Index(size);       // 根据申请的内存大小，计算出对应的哈希桶的下标
    if (!_freeLists[index].Empty())              // 如果对应的哈希桶（也就是_freeList不为空）不为空，
                                                 // 代表有空闲的内存，直接从哈希桶中申请内存
    {
        return _freeLists[index].Pop(); // 调用FreeList的Pop函数，从哈希桶中申请内存
    }
    else // 如果对应的哈希桶为空，也就是说没有空闲的内存可以分配
    {
        return FetchFromCentralCache(index, alignSize); // 返回从中心缓存申请到的内存
    }
}

// ThreadCache用来释放内存的函数
// 参数1：ptr-要释放的内存指针
// 参数2：size-要释放的内存大小
void ThreadCache::Deallocate(void *ptr, size_t size)
{
    assert(ptr);
    assert(size <= MAX_BYTES);

    size_t index = SizeClass::Index(size); // 找对应的自由链表的索引
    _freeLists[index].Push(ptr);           // 把要释放的内存指针，插入到对应的自由链表中

    if (_freeLists[index].Size() >= _freeLists[index].MaxSize())
    {
        ListTooLong(_freeLists[index], size);
    }
}

// 从中心缓存获取内存的函数
// 参数1：index-哈希桶的下标
// 参数2：size-要申请的内存大小
// 返回值：void*类型的指针，指向申请到的内存
void *ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
    // 慢开始的调节算法
    // 控制达到了两个点：
    // 1. 最开始不会一次向central cache一次批量要太多，因为要太多了可能用不完
    // 2. 如果你不要这个size大小内存需求，那么batchNum就会不断增长，直到上限
    // 3. size越大，一次向central cache要的batchNum就越小
    // 4. size越小，一次向central cache要的batchNum就越大
    size_t batchNum = min(_freeLists[index].MaxSize(), SizeClass::NumMoveSize(size));
    if (_freeLists[index].MaxSize() == batchNum)
    {
        _freeLists[index].MaxSize() += 1;
    }
    void *start = nullptr;
    void *end = nullptr;
    size_t actualNum = CentralCache::GetInstance()->FetchRangeObj(start, end, batchNum, size);
    assert(actualNum >= 1);

    if (actualNum == 1)
    {
        assert(start == end);
        return start;
    }
    else
    {
        _freeLists[index].PushRange(NextObj(start), end, actualNum - 1);
        return start;
    }

    return start;
}

void ThreadCache::ListTooLong(FreeList &list, size_t size)
{
    void *start = nullptr;
    void *end = nullptr;
    list.PopRange(start, end, list.MaxSize());

    CentralCache::GetInstance()->ReleaseListToSpans(start, size);
}